//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

use datetime::{DatePiece, LocalDateTime};
use git2::{BranchType, Commit, DiffStats, Error, Oid, Repository};
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

#[derive(Serialize)]
struct CommitInfo {
    commit_id: String,
    summary: String,
    add_count: usize,
    del_count: usize,
}

#[derive(Serialize)]
struct StatItem {
    commit_count: usize,
    total_add_count: usize,
    total_del_count: usize,
    max_commit: CommitInfo,
    min_commit: CommitInfo,
}

#[derive(Serialize, Clone)]
struct DayStatItem {
    day_str: String,
    commit_count: usize,
    add_count: usize,
    del_count: usize,
}

#[derive(Serialize)]
struct CommiterStatItem {
    commiter: String,
    stat: StatItem,
    day_stat_list: Vec<DayStatItem>,
}

#[derive(Serialize)]
struct AnalyseInfo {
    global_stat: StatItem,
    effect_add_count: usize,
    effect_del_count: usize,
    commiter_stat_list: Vec<CommiterStatItem>,
    last_time: i64,
}

#[derive(Clone)]
struct CacheCommitInfo {
    commit_id: Oid,
    time_stamp: i64,
    parent_count: usize,
    commiter: String,
    summary: String,
    add_count: usize,
    del_count: usize,
}

fn do_list_commit(
    commit_map: &mut HashMap<String, CacheCommitInfo>,
    commit: &Commit,
) -> Result<(), Error> {
    let commit_id = commit.id().to_string();
    if commit_map.contains_key(&commit_id) {
        return Ok(());
    }
    let count = commit.parent_count();
    commit_map.insert(
        commit_id.clone(),
        CacheCommitInfo {
            commit_id: commit.id(),
            time_stamp: commit.time().seconds(),
            parent_count: count,
            commiter: String::from(commit.committer().name().unwrap_or_default()),
            summary: String::from(commit.summary().unwrap_or_default()),
            add_count: 0,
            del_count: 0,
        },
    );
    if count == 0 {
        return Ok(());
    }
    for index in 0..count {
        let parent_commit = commit.parent(index);
        if parent_commit.is_err() {
            return Err(parent_commit.err().unwrap());
        }
        let parent_commit = parent_commit.unwrap();
        let result = do_list_commit(commit_map, &parent_commit);
        if result.is_err() {
            return result;
        }
    }
    return Ok(());
}

fn get_commit_diff(repo: &Repository, commit_id: &Oid) -> Result<DiffStats, Error> {
    let commit = repo.find_commit(commit_id.clone());
    if commit.is_err() {
        return Err(commit.err().unwrap());
    }
    let commit = commit.unwrap();
    let new_tree = commit.tree();
    if new_tree.is_err() {
        return Err(new_tree.err().unwrap());
    }
    let new_tree = new_tree.unwrap();
    if commit.parent_count() == 0 {
        let diff = repo.diff_tree_to_tree(None, Some(&new_tree), None);
        if diff.is_err() {
            return Err(diff.err().unwrap());
        }
        let diff = diff.unwrap();
        let diff_stat = diff.stats();
        if diff_stat.is_err() {
            return Err(diff_stat.err().unwrap());
        }
        return Ok(diff_stat.unwrap());
    } else {
        let parent_commit = commit.parent(0);
        if parent_commit.is_err() {
            return Err(parent_commit.err().unwrap());
        }
        let parent_commit = parent_commit.unwrap();
        let old_tree = parent_commit.tree();
        if old_tree.is_err() {
            return Err(old_tree.err().unwrap());
        }
        let old_tree = old_tree.unwrap();
        let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None);
        if diff.is_err() {
            return Err(diff.err().unwrap());
        }
        let diff = diff.unwrap();
        let diff_stat = diff.stats();
        if diff_stat.is_err() {
            return Err(diff_stat.err().unwrap());
        }
        return Ok(diff_stat.unwrap());
    }
}

fn calc_effect_count(
    repo: &Repository,
    add_count: &mut usize,
    del_count: &mut usize,
    commit_list: &Vec<CacheCommitInfo>,
) {
    if commit_list.len() == 0 {
        return;
    }
    let new_commit = repo.find_commit(commit_list[0].commit_id.clone());
    if new_commit.is_err() {
        return;
    }
    let new_commit = new_commit.unwrap();
    let new_tree = new_commit.tree();
    if new_tree.is_err() {
        return;
    }
    let new_tree = new_tree.unwrap();
    //找到最早的commit
    let commit = repo.find_commit(commit_list[commit_list.len() - 1].commit_id.clone());
    if commit.is_err() {
        return;
    }
    let commit = commit.unwrap();
    if commit.parent_count() == 0 {
        let diff = repo.diff_tree_to_tree(None, Some(&new_tree), None);
        if diff.is_err() {
            return;
        }
        let diff = diff.unwrap();
        let diff_stat = diff.stats();
        if diff_stat.is_err() {
            return;
        }
        let diff_stat = diff_stat.unwrap();
        *add_count = diff_stat.insertions();
        *del_count = diff_stat.deletions();
    } else {
        let parent_commit = commit.parent(0);
        if parent_commit.is_err() {
            return;
        }
        let parent_commit = parent_commit.unwrap();
        let old_tree = parent_commit.tree();
        if old_tree.is_err() {
            return;
        }
        let old_tree = old_tree.unwrap();
        let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None);
        if diff.is_err() {
            return;
        }
        let diff = diff.unwrap();
        let diff_stat = diff.stats();
        if diff_stat.is_err() {
            return;
        }
        let diff_stat = diff_stat.unwrap();
        *add_count = diff_stat.insertions();
        *del_count = diff_stat.deletions();
    }
}

fn calc_stat(commit_list: &Vec<CacheCommitInfo>) -> StatItem {
    let mut stat = StatItem {
        commit_count: commit_list.len(),
        total_add_count: 0,
        total_del_count: 0,
        max_commit: CommitInfo {
            commit_id: "".into(),
            summary: "".into(),
            add_count: 0,
            del_count: 0,
        },
        min_commit: CommitInfo {
            commit_id: "".into(),
            summary: "".into(),
            add_count: 0,
            del_count: 0,
        },
    };
    for commit in commit_list {
        stat.total_add_count += commit.add_count;
        stat.total_del_count += commit.del_count;
    }

    for commit in commit_list {
        if commit.add_count + commit.del_count == 0 {
            continue;
        }
        if stat.max_commit.commit_id == "" {
            stat.max_commit = CommitInfo {
                commit_id: commit.commit_id.to_string(),
                summary: commit.summary.clone(),
                add_count: commit.add_count,
                del_count: commit.del_count,
            }
        }
        if stat.min_commit.commit_id == "" {
            stat.min_commit = CommitInfo {
                commit_id: commit.commit_id.to_string(),
                summary: commit.summary.clone(),
                add_count: commit.add_count,
                del_count: commit.del_count,
            }
        }
        if (commit.add_count + commit.del_count)
            > (stat.max_commit.add_count + stat.max_commit.del_count)
        {
            stat.max_commit = CommitInfo {
                commit_id: commit.commit_id.to_string(),
                summary: commit.summary.clone(),
                add_count: commit.add_count,
                del_count: commit.del_count,
            }
        }
        if (commit.add_count + commit.del_count)
            < (stat.min_commit.add_count + stat.min_commit.del_count)
        {
            stat.min_commit = CommitInfo {
                commit_id: commit.commit_id.to_string(),
                summary: commit.summary.clone(),
                add_count: commit.add_count,
                del_count: commit.del_count,
            }
        }
    }
    if (stat.min_commit.add_count + stat.min_commit.del_count)
        == (stat.max_commit.add_count + stat.max_commit.del_count)
    {
        stat.min_commit = CommitInfo {
            commit_id: "".into(),
            summary: "".into(),
            add_count: 0,
            del_count: 0,
        };
        stat.max_commit = CommitInfo {
            commit_id: "".into(),
            summary: "".into(),
            add_count: 0,
            del_count: 0,
        };
    }
    return stat;
}

fn calc_day_stat(commit_list: &Vec<CacheCommitInfo>) -> Vec<DayStatItem> {
    let mut map: HashMap<String, DayStatItem> = HashMap::new();
    for commit in commit_list {
        let date = LocalDateTime::at(commit.time_stamp);
        let day_str = format!(
            "{}",
            date.year() * 10000 + date.month() as i64 * 100 + date.day() as i64
        );
        let stat = map.get_mut(&day_str);
        match stat {
            Some(item) => {
                item.commit_count += 1;
                item.add_count += commit.add_count;
                item.del_count += commit.del_count;
            }
            None => {
                map.insert(
                    day_str.clone(),
                    DayStatItem {
                        day_str: day_str,
                        commit_count: 1,
                        add_count: commit.add_count,
                        del_count: commit.del_count,
                    },
                );
            }
        }
    }
    let mut stat_list: Vec<DayStatItem> = map.into_values().collect();
    stat_list.sort_by(|a, b| a.day_str.cmp(&b.day_str));
    return stat_list;
}

fn list_commit(
    repo: &Repository,
    branch_name: &String,
    from_time: &i64,
    to_time: &i64,
) -> Result<(Vec<CacheCommitInfo>, i64), Error> {
    let branch = repo.find_branch(&branch_name, BranchType::Local);
    if branch.is_err() {
        return Err(branch.err().unwrap());
    }
    let branch = branch.unwrap();
    let commit = branch.get().peel_to_commit();
    if commit.is_err() {
        return Err(commit.err().unwrap());
    }
    let commit = commit.unwrap();
    let mut commit_map: HashMap<String, CacheCommitInfo> = HashMap::new();
    let result = do_list_commit(&mut commit_map, &commit);
    if result.is_err() {
        return Err(result.err().unwrap());
    }
    let mut last_time = 0_i64;
    let mut commit_list: Vec<CacheCommitInfo> = Vec::new();
    for item in commit_map.into_values() {
        if item.time_stamp > last_time {
            last_time = item.time_stamp;
        }
        if item.time_stamp < *from_time || item.time_stamp > *to_time || item.parent_count == 2 {
            continue;
        }
        commit_list.push(item);
    }
    commit_list.sort_by(|a, b| b.time_stamp.cmp(&a.time_stamp));
    return Ok((commit_list, last_time));
}

pub fn exec(
    git_path: PathBuf,
    branch_name: &String,
    from_time: &i64,
    to_time: &i64,
) -> Result<(), Error> {
    let repo = Repository::open(git_path);
    if repo.is_err() {
        return Err(repo.err().unwrap());
    }
    let repo = repo.unwrap();

    let commit_list = list_commit(&repo, branch_name, from_time, to_time);
    if commit_list.is_err() {
        return Err(commit_list.err().unwrap());
    }
    let (mut commit_list, last_time) = commit_list.unwrap();
    for commit_index in 0..commit_list.len() {
        let commit = &commit_list[commit_index];
        let commit_diff = get_commit_diff(&repo, &commit.commit_id);
        if commit_diff.is_err() {
            continue;
        }
        let commit_diff = commit_diff.unwrap();
        commit_list[commit_index].add_count = commit_diff.insertions();
        commit_list[commit_index].del_count = commit_diff.deletions();
    }

    //计算总统计数据
    let global_stat = calc_stat(&commit_list);
    //计算有效修改
    let mut effect_add_count: usize = 0;
    let mut effect_del_count: usize = 0;
    calc_effect_count(
        &repo,
        &mut effect_add_count,
        &mut effect_del_count,
        &commit_list,
    );
    //计算用户修改
    let mut commiter_set = HashSet::new();
    for commit in &commit_list {
        commiter_set.insert(commit.commiter.clone());
    }
    let mut commiter_stat_list = Vec::new();
    for commiter in &commiter_set {
        let mut tmp_list: Vec<CacheCommitInfo> = Vec::new();
        for commit in &commit_list {
            if &commit.commiter == commiter {
                tmp_list.push(commit.clone());
            }
        }
        let commiter_stat = calc_stat(&tmp_list);
        let day_stat_list = calc_day_stat(&tmp_list);
        commiter_stat_list.push(CommiterStatItem {
            commiter: commiter.clone(),
            stat: commiter_stat,
            day_stat_list: day_stat_list,
        });
    }
    let info = AnalyseInfo {
        global_stat: global_stat,
        effect_add_count: effect_add_count,
        effect_del_count: effect_del_count,
        commiter_stat_list: commiter_stat_list,
        last_time: last_time,
    };
    println!("{}", serde_json::to_string(&info).unwrap());
    return Ok(());
}
